<template>
  <div id="Container">
    <!-- 显示信息 -->
    <div id="MapInfo" v-if="IsShowInfo">
      <div>Scales: {{imgScale}}</div>
      <div>CanvasPos:[{{CanvasPos.x}}, {{CanvasPos.y}}]</div>
      <div>ImagePos: [{{ImagePos.x}}, {{ImagePos.y}}]</div>
      <div>ImgStart: [{{ImagStartX}}, {{ImagStartY}}]</div>
      <div>{{info}}</div>
    </div>

    <!-- 上层组件 -->
    <div
      v-for="(item,index) in showDevices"
      :key="index"
      class="devices"
      :id="'devices-'+index"
      :ref="'devices-'+index"
      :style="item.style"
      @click="ControlDevice"
      @dragstart="dragStart($event,item)"
      draggable="true"
    >
      <div>
        <p class="img" ref="img">
          <img src="../assets/image/camare.svg" />
        </p>
        <!-- <p class="img"><img src="../assets/image/camare.svg"></p> -->
      </div>
    </div>

    <!-- 底图 -->
    <canvas
      id="baseCanvas"
      @drop="drop"
      @dragover="draging"
      @mouseup="mouseUpOrOut($event)"
      @mousedown="mouseDown($event)"
      @mousemove="mouseMove($event)"
      @contextmenu.prevent="contextMenu($event)"
      @mouseout="mouseUpOrOut($event)"
      @mousewheel="mouseWheel($event)"
    >
      <p>您的系统不支持此程序!</p>
    </canvas>

    <!-- 右键菜单 -->
    <div class="menu" v-show="isShow" ref="menu">
      <p class="title">添加设备</p>
      <div class="body">
        <div class="body_left">
          <el-select v-model="value" placeholder="请选择">
            <!-- <el-option label="1" value="1"></el-option> -->
          </el-select>
        </div>
        <div class="body_right">
          <p v-for="(item,i) of options" :key="i" class="p_potion">{{item.lable}}</p>
        </div>
      </div>
      <div class="footer">
        <p @click="MenuResult(true)">确定</p>
        <p @click="MenuResult(false)">取消</p>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "MapCanvas",
  data() {
    return {
      showDevices: [], // 显示的设备
      defaultDevW: 30, // 设备图标默认宽度
      defaultDevH: 30, // 设备图标默认宽度

      // 底图
      baseCanvas: null, // 画布
      baseContext: null, // 画布上下文

      img: null, // 图片对象
      ImagStartX: 0,  // 图片开始绘制的坐标
      ImagStartY: 0,  // 图片开始绘制的坐标

      // 缩放操作
      imgScale: 1,
      ScaleSpeed: 0.01,  // 缩放速度，每次缩放的百分百，到达最大缩放倍数的【总步数=1/ScaleSpeed】，范围：[0, 1]
      ScaleMax: 10,  // 允许缩放的最大倍数
      NowScaleDatle: 0,  // 当前缩放的变化量（方向*次数），正数为放大，负数为缩小

      pos: {},  // 拖动前坐标
      posl: {}, // 拖动后坐标

      CanvasPos: { x: 0, y: 0 },  // 参考系为画布的鼠标位置坐标
      ImagePos: { x: 0, y: 0 },   // 参考系为底图的鼠标位置坐标
      dragging: false,
      info: "",
      IsShowInfo: false,

      // 临时保存新建设备数据
      newDevData: {},
      dragDeviceObj: {},  // 当前拖拽对象

      // 右键菜单
      isShow: false,
      value: "",
      options: [
        { lable: 1 },
        { lable: 2 },
        { lable: 3 },
        { lable: 4 },
        { lable: 5 },
        { lable: 6 },
        { lable: 7 },
        { lable: 8 },
        { lable: 8 },
        { lable: 8 },
        { lable: 8 }
      ]
    };
  },
  props: {
    Devices: {
      type: Array,
      default() {
        return [];
      }
    },
    MapUrl: {
      type: String,
      default() {
        return "";
      }
    }
  },
  mounted() {
    this.baseCanvas = document.getElementById("baseCanvas");
    this.baseContext = this.baseCanvas.getContext("2d");
    // 初始化
    this.init();
    // 窗口大小改变后，重新初始化
    window.onresize = () => { this.init(); };
  },
  methods: {
    init() {
      // console.log("init");
        // 设置宽高为父节点宽高
      this.baseCanvas.width = this.baseCanvas.parentNode.clientWidth;
      this.baseCanvas.height = this.baseCanvas.parentNode.clientHeight;

      // 加载底图
      this.LoadBaseImg(this.MapUrl);
      // 画布绘制
      this.DrawImage();
    },
    // 更改设备的参数
    DeviceChange(oldObj, newObj) {
      let i = oldObj.index;
      this.Devices[i].x = newObj.x;
      this.Devices[i].y = newObj.y;
      this.DrawImage();  // 重新绘制
    },
    // 控制设备
    ControlDevice() {
      alert("打开摄像头");
    },

    // 添加设备
    AddDevice() {
      let info = this.newDevData;
      info.w = info.w ? info.w : this.defaultDevW; // 设备宽度
      info.h = info.h ? info.h : this.defaultDevH; // 设备高度      

      // 判断是否超出图片范围（当放大为最大时）
      if (
        info.x < info.w / 2 ||
        info.x + info.w / 2 > this.img.width*this.ScaleMax ||
        info.y < info.h / 2 ||
        info.y + info.h / 2 > this.img.height*this.ScaleMax
      ) {
        alert("已超出地图范围！");
        return;
      }
      info.x = info.x;
      info.y = info.y;

      this.Devices.push(info);
      this.DrawImage();
    },

    // 跳转到设备
    GotoDevice(info) {
      let x = info.x*this.imgScale/this.ScaleMax;
      let y = info.y*this.imgScale/this.ScaleMax;

      this.SetCenter(x, y);
    },

    // 设置某个点为视图中心
    SetCenter(x, y) {
      this.ImagStartX = this.baseCanvas.width / 2 - x;
      this.ImagStartY = this.baseCanvas.height / 2 - y;
      // console.log(x, y);

      this.DrawImage(); // 重新绘制
    },

    // 加载底图
    LoadBaseImg(src) {
      this.img = new Image();
      this.img.crossOrigin = "Anonymous"; //解决跨域问题
      this.img.onload = () => {
        // this.SetCenter(this.img.width / 2, this.img.height / 2); // 设置图片中心点为视图中心
        this.DrawImage(); // 绘制图片
      };
      
      this.img.src = src;
    },

    // 最核心的函数，调用率也最高。绘制：底图、上层控件
    DrawImage() {
      this.showDevices = [];
      this.baseContext.clearRect(0, 0, this.baseCanvas.width, this.baseCanvas.height);

      // 保证图片移动时不超出画布内
      // this.EnsureInCanvas();

      // 绘制上层组件
      this.Devices.forEach((item, index) => {
        let DevW = item.w ? item.w : this.defaultDevW; // 设备宽度
        let DevH = item.h ? item.h : this.defaultDevH; // 设备高度

        // 计算组件左上角点坐标，按照最大倍数来存储
        let dx = item.x*this.imgScale/this.ScaleMax + this.ImagStartX - DevW / 2;
        let dy = item.y*this.imgScale/this.ScaleMax + this.ImagStartY - DevH / 2;

        if (dx >= 0 && dy >= 0 && dx + DevW <= this.baseCanvas.width && dy + DevH <= this.baseCanvas.height) {
          this.showDevices.push({
            name: item.name,
            index,
            x: item.x,  // 底图的 x
            y: item.y,  // 底图的 y
            canvasX: dx,// 画布的 x
            canvasY: dy,// 画布的 y
            style: {
              top: dy + "px",
              left: dx + "px",
              width: DevW + "px",
              height: DevH + "px"
            }
          });
        }
      });

      this.baseContext.drawImage(
        this.img, //规定要使用的图像、画布或视频。
        0, 0, // 图片的起始坐标
        this.img.width, this.img.height, //图片的终止坐标
        this.ImagStartX, this.ImagStartY, // 在画布上放置的起始坐标
        this.img.width * this.imgScale, this.img.height * this.imgScale, // 在画布上放置的终止坐标
      );

      // console.log('画布：', this.baseCanvas.width, this.baseCanvas.height, ' 起始：',  this.ImagStartX, this.ImagStartY, '终止：', this.img.width, this.img.height);
    },

    // 保证图片移动时不超出画布内
    EnsureInCanvas(){
      if (this.img.width*this.imgScale > this.baseCanvas.width) {
        // 图片宽度大于画布宽度
        if (this.ImagStartX < -(this.img.width*this.imgScale - this.baseCanvas.width)) {
          this.ImagStartX = -(this.img.width*this.imgScale - this.baseCanvas.width);
        }
      } else {
        this.ImagStartX = 0;
      }

      if (this.img.height*this.imgScale > this.baseCanvas.height) {
        // 图片高度大于画布高度
        if (this.ImagStartY < -(this.img.height*this.imgScale - this.baseCanvas.height)) {
          this.ImagStartY = -(this.img.height*this.imgScale - this.baseCanvas.height);
        }
      } else {
        this.ImagStartY = 0;
      }

      if (this.ImagStartX > 0) {
        this.ImagStartX = 0;
      }

      if (this.ImagStartY > 0) {
        this.ImagStartY = 0;
      }
    },

    // 坐标转换
    windowToCanvas(x, y) {
      //获取canvas元素的边距对象
      let bbox = this.baseCanvas.getBoundingClientRect();
      //返回一个坐标对象
      //类似json的一种写法
      return {
        x: (x - bbox.left) * (this.baseCanvas.width / bbox.width),
        y: (y - bbox.top) * (this.baseCanvas.height / bbox.height)
      };
    },

    // 鼠标按下事件处理
    mouseDown(event) {
      // 鼠标左键
      if (event.which == 1) {
        this.dragging = true;
        this.pos = this.windowToCanvas(event.clientX, event.clientY); //坐标转换，将窗口坐标转换成canvas的坐标
      }
    },
    mouseUpOrOut(event) {
      this.dragging = false;
    },

    // 鼠标移动事件处理
    mouseMove(event) {
      this.CanvasPos = this.windowToCanvas(event.clientX, event.clientY);
      this.ImagePos = {x: this.CanvasPos.x-this.ImagStartX, y: this.CanvasPos.y-this.ImagStartY}
      
      if (this.dragging) {
        this.posl = this.CanvasPos;
        let x = this.posl.x - this.pos.x,
          y = this.posl.y - this.pos.y;
        this.ImagStartX += x;
        this.ImagStartY += y;
        this.pos = JSON.parse(JSON.stringify(this.posl));
        this.DrawImage(); //重新绘制图片
      }
    },

    // 鼠标滚轮事件，核心公式：缩放比例 = 1 + 当前步数 / 总步数 * ( 这个方向的最大缩放值 - 1)
    mouseWheel (event) {
      // 更新底图坐标
      this.ImagePos = {x: this.CanvasPos.x-this.ImagStartX, y: this.CanvasPos.y-this.ImagStartY}

      //获取当前鼠标的滚动情况
      let wheelDelta = event.wheelDelta ? event.wheelDelta : event.deltalY;
      // 从全局中取
      let NowScaleDatle = this.NowScaleDatle;

      // 记录鼠标滚轮变化量
      if (wheelDelta > 0){
        NowScaleDatle += 1;
      } else {
        NowScaleDatle -= 1;  
      }
      // 超出范围则返回
      if (Math.abs(NowScaleDatle)>(1/this.ScaleSpeed)){
        return;
      }

      let StepCount = Math.abs(NowScaleDatle);  // 当前步数
      let TotalStep = 1/this.ScaleSpeed; // 总步数
      let Direction = NowScaleDatle?NowScaleDatle/Math.abs(NowScaleDatle):0;  // 方向
      let ScaleMaxV = Math.pow(this.ScaleMax, Direction);  // 这个方向的最大缩放值
      // 缩放比例 = 1 + 当前步数 / 总步数 * ( 这个方向的最大缩放值 - 1 )
      let ImgScale = 1 + StepCount/TotalStep*(ScaleMaxV-1);

      // 计算偏移，注意区分：【现在缩放比例】和【将来缩放比例】—— this.imgScale 和 ImgScale
      let scaleImgPosX = this.ImagePos.x*ImgScale/this.imgScale;
      let scaleImgPosY = this.ImagePos.y*ImgScale/this.imgScale;
      
      // 当前画布位置上的图片点
      let nowImgPosX = this.ImagePos.x;
      let nowImgPosY = this.ImagePos.y;

      // 与目标图片点的差值
      let deltaX = (nowImgPosX - scaleImgPosX)*this.imgScale/this.imgScale;
      let deltaY = (nowImgPosY - scaleImgPosY)*this.imgScale/this.imgScale;
    
      // 设置偏移值
      this.ImagStartX += deltaX;
      this.ImagStartY += deltaY;

      // 设置到全局中
      this.NowScaleDatle = NowScaleDatle;
      this.imgScale = ImgScale;
      this.ImagePos = {x: this.CanvasPos.x-this.ImagStartX, y: this.CanvasPos.y-this.ImagStartY}

      // 重绘
      this.DrawImage();
    },

    // 鼠标右键菜单
    contextMenu($event) {
      // 获取当前鼠标的位置
      let p = this.windowToCanvas(event.clientX, event.clientY);
      let x = p.x - this.ImagStartX;
      let y = p.y - this.ImagStartY;

      // 临时保存数据，待菜单点击ok时使用
      this.newDevData = { name: "新增设备", x: x/(this.imgScale/this.ScaleMax), y: y/(this.imgScale/this.ScaleMax) };

      // 设置菜单显示
      this.isShow = true;

      // 设置菜单位置
      this.$nextTick(() => {
        let menuX = p.x;
        let menuY = p.y;

        // 获取菜单的宽高
        let meunW = this.$refs.menu.offsetWidth;
        let menuH = this.$refs.menu.offsetHeight;

        if (menuX + meunW > this.baseCanvas.width) {
          menuX = p.x - meunW;
        }

        if (menuY + menuH > this.baseCanvas.height) {
          menuY = p.y - menuH;
        }
        // 设置
        this.$refs.menu.style.left = menuX + "px";
        this.$refs.menu.style.top = menuY + "px";
      });
    },

    // 设置图片位置到画布位置，使两点重合
    SetImgPosToCanvasPos(imgPos, canvasPos){
      // 判断是否超出画布范围
      if (canvasPos.x<0 || canvasPos.x>this.baseCanvas.width-1){
        return;
      }
      if (canvasPos.y<0 || canvasPos.y>this.baseCanvas.height-1){
        return;
      }
      
      // 当前画布位置上的图片点
      let nowImgPosX = canvasPos.x-this.ImagStartX;
      let nowImgPosY = canvasPos.y-this.ImagStartY;

      // 与目标图片点的差值
      let deltaX = nowImgPosX - imgPos.x;
      let deltaY = nowImgPosY - imgPos.y;

      // 设置偏移值
      this.ImagStartX += deltaX;
      this.ImagStartY += deltaY;
    },
    // 菜单点【确定】/【取消】时触发
    MenuResult(IsTrue) {
      this.isShow = false;
      if (IsTrue) {
        this.AddDevice();
      }
    },

    /* // 允许拖拽
    allowDrop(ev) {
      ev.preventDefault();
    }, */

    // 拖动开始
    dragStart(e,item) {
      // console.log("拖动开始");
      this.dragDeviceObj = {
        offsetX: e.offsetX, // [鼠标落点]相对于[目标元素]左上角的[偏移]
        offsetY: e.offsetY, // [鼠标落点]相对于[目标元素]左上角的[偏移]
        oldObj: item,
      };
    },
    drop(e) {
      // console.log("拖拽结束", e);
      // [鼠标落点]相对于[目标元素]左上角的[偏移]
      let offsetX = this.dragDeviceObj.offsetX;
      let offsetY = this.dragDeviceObj.offsetY;
      
      // 拖拽前的《画布》坐标
      let beforeDropX = this.dragDeviceObj.oldObj.canvasX;
      let beforeDropY = this.dragDeviceObj.oldObj.canvasY;

      // 拖拽后的《画布》坐标
      let afterDropX = e.offsetX;
      let afterDropY = e.offsetY;

      // 计算坐标变化值
      let dx = afterDropX - beforeDropX - offsetX;
      let dy = afterDropY - beforeDropY - offsetY;
      
      let newObj = JSON.parse(JSON.stringify(this.dragDeviceObj.oldObj)); // 深拷贝
      newObj.x = this.dragDeviceObj.oldObj.x + dx/(this.imgScale/this.ScaleMax);
      newObj.y = this.dragDeviceObj.oldObj.y + dy/(this.imgScale/this.ScaleMax);

      this.DeviceChange(this.dragDeviceObj.oldObj, newObj);

    },
    // 拖动中
    draging(e) {
      // console.log("拖动中", e);
      e.preventDefault();
    }
  }
};
</script>

<style lang="scss">
#Container {
  position: relative;
  user-select: none;
  height: 100%;
  width: 100%;
}

#baseCanvas {
  background: #0d2a5e;
  height: 100%;
  width: 100%;
}

.devices {
  position: absolute;
}

.devices button {
  width: 100%;
  height: 100%;
}

#MapInfo {
  position: absolute;
  right: 5px;
  top: 0;
  color: red;
  text-align: left;
}

.menu {
  position: absolute;
  min-width: 300px;
  min-height: 200px;
  border-radius: 4px;
  background: #0126428c;
  color: #fff;
  font-size: 16px;
  padding: 10px;
  .title {
    text-align: center;
  }
  .body {
    min-height: 150px;
    margin: 30px 0;
    display: flex;
    .body_left {
      width: 180px;
      .el-select {
        width: 60%;
        height: 28px;
        margin-right: 10px;
        .el-input {
          height: 100%;
          .el-input__inner {
            height: 100%;
            // line-height: 28px;
            background: #121c3a;
            border: 1px solid #041126;
            color: #cfd0d1;
          }
          .el-input__icon {
            line-height: 28px;
          }
        }
      }
    }
    .body_right {
      min-width: 180px;
      height: 200px;
      box-shadow: 0 0 10px #121c3a;
      overflow: auto;
      &::-webkit-scrollbar {
        width: 5px;
      }
      &::-webkit-scrollbar-track {
        background-color: #041126;
        -webkit-border-radius: 2em;
        -moz-border-radius: 2em;
        border-radius: 2em;
      }
      &::-webkit-scrollbar-thumb {
        background-color: #0f3856;
        -webkit-border-radius: 2em;
        -moz-border-radius: 2em;
        border-radius: 2em;
      }
      .p_potion {
        height: 24px;
        line-height: 24px;
        color: #cfd0d1;
        background: #0f3856;
        padding: 0 10px;
      }
    }
  }
  .footer {
    display: flex;
    justify-content: space-around;
    p {
      cursor: pointer;
      width: 50%;
      text-align: center;
      padding-top: 5px;
      border-top: 1px solid #0a1c2a;
      &:active {
        color: rgb(5, 8, 185);
      }
      &:first-child {
        border-right: 1px solid #0a1c2a;
      }
    }
  }
}

.img {
  width: 30px;
  height: 30px;
  margin: 0;
  img {
    width: 100%;
    height: 100%;
  }
}
.el-select-dropdown {
  background: #121c3a;
  border: 1px solid #041126;
  .el-select-dropdown__item {
    color: #cfd0d1;
    height: 24px;
    line-height: 24px;
    &.hover {
      background: #0e266e;
    }
  }
  .popper__arrow {
    top: -8px !important;
    border-bottom-color: #121c3a !important;
  }
}
</style>